using System;
using System.Collections.Generic;
using System.Data.SqlTypes;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using Framework.Library.Core.Utils.Miscellaneous;

namespace Framework.Library.Core.Utils.Text
{
    public static class StringUtils
    {
        public const string CarriageReturnLineFeed = "\r\n";
        public const string Empty = "";
        public const char CarriageReturn = '\r';
        public const char LineFeed = '\n';
        public const char Tab = '\t';

        /// <summary>
        /// Determines whether the string contains white space.
        /// </summary>
        /// <param name="s">The string to test for white space.</param>
        /// <returns>
        /// 	<c>true</c> if the string contains white space; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsWhiteSpace(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            for (int i = 0; i < s.Length; i++)
            {
                if (char.IsWhiteSpace(s[i]))
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Determines whether the string is all white space. Empty string will return false.
        /// </summary>
        /// <param name="s">The string to test whether it is all white space.</param>
        /// <returns>
        /// 	<c>true</c> if the string is all white space; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsWhiteSpace(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            if (s.Length == 0)
                return false;

            for (int i = 0; i < s.Length; i++)
            {
                if (!char.IsWhiteSpace(s[i]))
                    return false;
            }

            return true;
        }

        /// <summary>
        /// Ensures the target string ends with the specified string.
        /// </summary>
        /// <param name="target">The target.</param>
        /// <param name="value">The value.</param>
        /// <returns>The target string with the value string at the end.</returns>
        public static string EnsureEndsWith(string target, string value)
        {
            if (target == null)
                throw new ArgumentNullException("target");

            if (value == null)
                throw new ArgumentNullException("value");

            if (target.Length >= value.Length)
            {
                if (
                    string.Compare(target, target.Length - value.Length, value, 0, value.Length,
                                   StringComparison.OrdinalIgnoreCase) ==
                    0)
                    return target;

                string trimmedString = target.TrimEnd(null);

                if (string.Compare(trimmedString, trimmedString.Length - value.Length, value, 0, value.Length,
                                   StringComparison.OrdinalIgnoreCase) == 0)
                    return target;
            }

            return target + value;
        }

        /// <summary>
        /// Determines whether the SqlString is null or empty.
        /// </summary>
        /// <param name="s">The string.</param>
        /// <returns>
        /// 	<c>true</c> if the SqlString is null or empty; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmpty(SqlString s)
        {
            if (s.IsNull)
                return true;
            else
                return string.IsNullOrEmpty(s.Value);
        }

        /// <summary>
        /// Determines whether the given string is null or empty or whitespace.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <returns>
        /// 	<c>true</c> if the given string is null or empty or whitespace; otherwise, <c>false</c>.
        /// </returns>
        public static bool IsNullOrEmptyOrWhiteSpace(string s)
        {
            if (string.IsNullOrEmpty(s))
                return true;
            else if (IsWhiteSpace(s))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Indents the specified string.
        /// </summary>
        /// <param name="s">The string to indent.</param>
        /// <param name="indentation">The number of characters to indent by.</param>
        /// <returns></returns>
        public static string Indent(string s, int indentation)
        {
            return Indent(s, indentation, ' ');
        }

        /// <summary>
        /// Indents the specified string.
        /// </summary>
        /// <param name="s">The string to indent.</param>
        /// <param name="indentation">The number of characters to indent by.</param>
        /// <param name="indentChar">The indent character.</param>
        /// <returns></returns>
        public static string Indent(string s, int indentation, char indentChar)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            if (indentation <= 0)
                throw new ArgumentException("Must be greater than zero.", "indentation");

            var sr = new StringReader(s);
            var sw = new StringWriter();

            ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line)
                                             {
                                                 tw.Write(new string(indentChar, indentation));
                                                 tw.Write(line);
                                             });

            return sw.ToString();
        }

        private static void ActionTextReaderLine(TextReader textReader, TextWriter textWriter, ActionLine lineAction)
        {
            string line;
            bool firstLine = true;
            while ((line = textReader.ReadLine()) != null)
            {
                if (!firstLine)
                    textWriter.WriteLine();
                else
                    firstLine = false;

                lineAction(textWriter, line);
            }
        }

        /// <summary>
        /// Numbers the lines.
        /// </summary>
        /// <param name="s">The string to number.</param>
        /// <returns></returns>
        public static string NumberLines(string s)
        {
            if (s == null)
                throw new ArgumentNullException("s");

            var sr = new StringReader(s);
            var sw = new StringWriter();

            int lineNumber = 1;

            ActionTextReaderLine(sr, sw, delegate(TextWriter tw, string line)
                                             {
                                                 tw.Write(lineNumber.ToString().PadLeft(4));
                                                 tw.Write(". ");
                                                 tw.Write(line);

                                                 lineNumber++;
                                             });

            return sw.ToString();
        }

        /// <summary>
        /// Nulls an empty string.
        /// </summary>
        /// <param name="s">The string.</param>
        /// <returns>Null if the string was null, otherwise the string unchanged.</returns>
        public static string NullEmptyString(string s)
        {
            return (string.IsNullOrEmpty(s)) ? null : s;
        }

        /// <summary>
        /// Replaces the new lines in a string with the given replacement characters.
        /// </summary>
        /// <param name="s">The string to replace new lines in.</param>
        /// <param name="replacement">The replacement characters.</param>
        /// <returns></returns>
        public static string ReplaceNewLines(string s, string replacement)
        {
            var sr = new StringReader(s);
            var sb = new StringBuilder();

            bool first = true;

            string line;
            while ((line = sr.ReadLine()) != null)
            {
                if (first)
                    first = false;
                else
                    sb.Append(replacement);

                sb.Append(line);
            }

            return sb.ToString();
        }

        public static string RemoveHtml(string s)
        {
            return RemoveHtmlInternal(s, null);
        }

        public static string RemoveHtml(string s, IList<string> removeTags)
        {
            if (removeTags == null)
                throw new ArgumentNullException("removeTags");

            return RemoveHtmlInternal(s, removeTags);
        }

        private static string RemoveHtmlInternal(string s, IList<string> removeTags)
        {
            List<string> removeTagsUpper = null;

            if (removeTags != null)
            {
                removeTagsUpper = new List<string>(removeTags.Count);

                foreach (string tag in removeTags)
                {
                    removeTagsUpper.Add(tag.ToUpperInvariant());
                }
            }

            var anyTag = new Regex(@"<[/]{0,1}\s*(?<tag>\w*)\s*(?<attr>.*?=['""].*?[""'])*?\s*[/]{0,1}>",
                                   RegexOptions.Compiled);

            return anyTag.Replace(s, delegate(Match match)
                                         {
                                             string tag = match.Groups["tag"].Value.ToUpperInvariant();

                                             if (removeTagsUpper == null)
                                                 return string.Empty;
                                             else if (removeTagsUpper.Contains(tag))
                                                 return string.Empty;
                                             else
                                                 return match.Value;
                                         });
        }

        /// <summary>
        /// Truncates the specified string.
        /// </summary>
        /// <param name="s">The string to truncate.</param>
        /// <param name="maximumLength">The maximum length of the string before it is truncated.</param>
        /// <returns></returns>
        public static string Truncate(string s, int maximumLength)
        {
            return Truncate(s, maximumLength, "...");
        }

        /// <summary>
        /// Truncates the specified string.
        /// </summary>
        /// <param name="s">The string to truncate.</param>
        /// <param name="maximumLength">The maximum length of the string before it is truncated.</param>
        /// <param name="suffix">The suffix to place at the end of the truncated string.</param>
        /// <returns></returns>
        public static string Truncate(string s, int maximumLength, string suffix)
        {
            if (suffix == null)
                throw new ArgumentNullException("suffix");

            if (maximumLength <= 0)
                throw new ArgumentException("Maximum length must be greater than zero.", "maximumLength");

            int subStringLength = maximumLength - suffix.Length;

            if (subStringLength <= 0)
                throw new ArgumentException("Length of suffix string is greater or equal to maximumLength");

            if (s != null && s.Length > maximumLength)
            {
                string truncatedString = s.Substring(0, subStringLength);
                // incase the last character is a space
                truncatedString = truncatedString.TrimEnd();
                truncatedString += suffix;

                return truncatedString;
            }
            else
            {
                return s;
            }
        }

        /// <summary>
        /// Creates a StringWriter with the specified capacity.
        /// </summary>
        /// <param name="capacity">The capacity of the StringWriter.</param>
        /// <returns></returns>
        public static StringWriter CreateStringWriter(int capacity)
        {
            var sb = new StringBuilder(capacity);
            var sw = new StringWriter(sb);

            return sw;
        }

        /// <summary>
        /// Gets the length of a string, returning null if the string is null.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        public static int? GetLength(string value)
        {
            if (value == null)
                return null;
            else
                return value.Length;
        }

        /// <summary>
        /// Returns the specified char's unicode string.
        /// </summary>
        /// <param name="c">The c.</param>
        /// <returns></returns>
        public static string ToCharAsUnicode(char c)
        {
            using (var w = new StringWriter())
            {
                WriteCharAsUnicode(w, c);
                return w.ToString();
            }
        }

        /// <summary>
        /// Writes the specified char's unicode string to a TextWriter.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="c">The c.</param>
        public static void WriteCharAsUnicode(TextWriter writer, char c)
        {
            ValidationUtils.ArgumentNotNull(writer, "writer");

            char h1 = MathUtils.IntToHex((c >> 12) & '\x000f');
            char h2 = MathUtils.IntToHex((c >> 8) & '\x000f');
            char h3 = MathUtils.IntToHex((c >> 4) & '\x000f');
            char h4 = MathUtils.IntToHex(c & '\x000f');

            writer.Write('\\');
            writer.Write('u');
            writer.Write(h1);
            writer.Write(h2);
            writer.Write(h3);
            writer.Write(h4);
        }

        #region Nested type: ActionLine

        private delegate void ActionLine(TextWriter textWriter, string line);

        #endregion

        public static int Similar(string s, string t)
        {
            if (string.IsNullOrEmpty(s))
            {
                if (string.IsNullOrEmpty(t))
                    return 0;
                return t.Length;
            }

            if (string.IsNullOrEmpty(t))
                return s.Length;
            s = s.ToLower();
            t = t.ToLower();

            int n = s.Length;
            int m = t.Length;
            int[,] d = new int[n + 1, m + 1];

            // initialize the top and right of the table to 0, 1, 2, ...
            for (int i = 0; i <= n; d[i, 0] = i++) ;
            for (int j = 1; j <= m; d[0, j] = j++) ;

            for (int i = 1; i <= n; i++)
            {
                for (int j = 1; j <= m; j++)
                {
                    int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
                    int min1 = d[i - 1, j] + 1;
                    int min2 = d[i, j - 1] + 1;
                    int min3 = d[i - 1, j - 1] + cost;
                    d[i, j] = Math.Min(Math.Min(min1, min2), min3);
                }
            }
            return d[n, m];
        }
    }
}